package main

import (
	"fmt"
	"sync"
)

/*
	条件变量怎样与互斥锁配合使用？
	条件变量的初始化离不开互斥锁，并且它的方法有的也是基于互斥锁的。

	条件变量提供的方法有三个：等待通知（wait）、单发通知（signal）和广播通知（broadcast）。

问题 1：条件变量的Wait方法做了什么？
	条件变量的Wait方法主要做了四件事。
		1.把调用它的 goroutine（也就是当前的 goroutine）加入到当前条件变量的通知队列中。
		2.解锁当前的条件变量基于的那个互斥锁。
		3.让当前的 goroutine 处于等待状态，等到通知到来时再决定是否唤醒它。此时，这个 goroutine 就会阻塞在调用这个Wait方法的那行代码上。
		4.如果通知到来并且决定唤醒这个 goroutine，那么就在唤醒它之后重新锁定当前条件变量基于的互斥锁。自此之后，当前的 goroutine 就会继续执行后面的代码了。

问题 2：条件变量的Signal方法和Broadcast方法有哪些异同？
	条件变量的Signal方法和Broadcast方法都是被用来发送通知的，
	不同的是，前者的通知只会唤醒一个因此而等待的 goroutine，而后者的通知却会唤醒所有为此等待的 goroutine。

	条件变量的Wait方法总会把当前的 goroutine 添加到通知队列的队尾，
	而它的Signal方法总会从通知队列的队首开始，查找可被唤醒的 goroutine。
	所以，因Signal方法的通知，而被唤醒的 goroutine 一般都是最早等待的那一个。
 */

/*
	变量mailbox代表信箱，是uint8类型的。
	若它的值为0则表示信箱中没有情报，而当它的值为1时则说明信箱中有情报
 */
var mailbox uint8

// lock是一个类型为sync.RWMutex的变量，是一个读写锁
var lock sync.RWMutex

var sendCond =sync.NewCond(&lock)

var recvCond =sync.NewCond(&lock)

var wg sync.WaitGroup

func main() {

	wg.Add(2)
	go sendToMail()
	go recvFromMail()

	wg.Wait()

	fmt.Println("main进入阻塞状态.... 等待wg中的子goroutine结束....")
}

/*
	发送邮件
 */
func sendToMail() {
	defer wg.Done()
	lock.Lock()
	for mailbox == 1 {
		sendCond.Wait()
	}
	fmt.Println("投递邮件到邮箱......")
	mailbox = 1
	lock.Unlock()
	fmt.Println("唤醒取出邮件......")
	recvCond.Signal()
}

/*
	接收邮件
*/
func recvFromMail() {
	defer wg.Done()
	lock.Lock()
	for mailbox == 0 {
		recvCond.Wait()
	}
	fmt.Println("从邮箱取出邮件......")
	mailbox = 0
	lock.Unlock()
	fmt.Println("唤醒投递邮件......")
	sendCond.Signal()
}
